/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <gdbus.h>

#include "powermanager.h"
#include "connman.h"

#define	_DBG_MANAGER(fmt, arg...)	DBG(DBG_MANAGER, fmt, ## arg)

static void append_profiles(DBusMessageIter *dict)
{
	connman_dbus_dict_append_variant_array(dict, "Profiles",
	    DBUS_TYPE_OBJECT_PATH, __connman_profile_list, NULL);
}

static void append_services(DBusMessageIter *dict)
{
	connman_dbus_dict_append_variant_array(dict, "Services",
	    DBUS_TYPE_OBJECT_PATH, __connman_service_list, NULL);
}

static void append_watchlist_services(DBusMessageIter *dict)
{
	connman_dbus_dict_append_variant_array(dict, "ServiceWatchList",
	    DBUS_TYPE_OBJECT_PATH, __connman_service_watchlist, NULL);
}

static void _append_devices(DBusMessageIter *dict, void *arg)
{
	__connman_element_list(NULL, CONNMAN_ELEMENT_TYPE_DEVICE, dict);
}
static void append_devices(DBusMessageIter *dict)
{
	connman_dbus_dict_append_variant_array(dict, "Devices",
	    DBUS_TYPE_OBJECT_PATH, _append_devices, NULL);
}

static void append_available_technologies(DBusMessageIter *dict)
{
	connman_dbus_dict_append_variant_array(dict, "AvailableTechnologies",
	    DBUS_TYPE_STRING, __connman_notifier_list_registered, NULL);
}

static void append_enabled_technologies(DBusMessageIter *dict)
{
	connman_dbus_dict_append_variant_array(dict, "EnabledTechnologies",
	    DBUS_TYPE_STRING, __connman_notifier_list_enabled, NULL);
}

static void append_connected_technologies(DBusMessageIter *dict)
{
	connman_dbus_dict_append_variant_array(dict, "ConnectedTechnologies",
	    DBUS_TYPE_STRING, __connman_notifier_list_connected, NULL);
}

static const char *online_state(void)
{
	return __connman_connection_is_online() == TRUE ? "online" : "offline";
}

static DBusMessage *get_properties(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	DBusMessage *reply;
	DBusMessageIter array, dict;
	connman_bool_t offlinemode;
	connman_bool_t arpgateway;
	const char *str;
	gchar *checkportal;

	_DBG_MANAGER("conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_PUBLIC) < 0)
		return __connman_error_permission_denied(msg);

	reply = dbus_message_new_method_return(msg);
	if (reply == NULL)
		return NULL;

	dbus_message_iter_init_append(reply, &array);

	dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	str = __connman_profile_active_path();
	if (str != NULL)
		connman_dbus_dict_append_variant(&dict, "ActiveProfile",
						DBUS_TYPE_OBJECT_PATH, &str);

	append_profiles(&dict);
	append_services(&dict);
	append_watchlist_services(&dict);

	append_devices(&dict);

	str = online_state();
	connman_dbus_dict_append_variant(&dict, "State",
						DBUS_TYPE_STRING, &str);

	str = connman_profile_get_country();
	if (str != NULL)
		connman_dbus_dict_append_variant(&dict, "Country",
					DBUS_TYPE_STRING, &str);

	offlinemode = __connman_profile_get_offlinemode();
	connman_dbus_dict_append_variant(&dict, "OfflineMode",
					DBUS_TYPE_BOOLEAN, &offlinemode);

	checkportal = __connman_service_mask_to_list(
	    __connman_profile_get_checkportal());
	if (checkportal != NULL) {
		connman_dbus_dict_append_variant(&dict, "CheckPortalList",
					DBUS_TYPE_STRING, &checkportal);
		g_free(checkportal);
	}

	str = __connman_profile_get_portal_url();
	connman_dbus_dict_append_variant(&dict, "PortalURL",
					DBUS_TYPE_STRING, &str);

	append_available_technologies(&dict);
	append_enabled_technologies(&dict);
	append_connected_technologies(&dict);

	str = __connman_service_default();
	if (str != NULL)
		connman_dbus_dict_append_variant(&dict, "DefaultTechnology",
						DBUS_TYPE_STRING, &str);

	arpgateway = connman_profile_get_arpgateway();
	connman_dbus_dict_append_variant(&dict, "ArpGateway",
					DBUS_TYPE_BOOLEAN, &arpgateway);

	dbus_message_iter_close_container(&array, &dict);

	return reply;
}

static DBusMessage *set_property(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	DBusMessageIter iter, value;
	const char *name;
	int type;

	_DBG_MANAGER("conn %p", conn);

	if (dbus_message_iter_init(msg, &iter) == FALSE)
		return __connman_error_invalid_arguments(msg);

	dbus_message_iter_get_basic(&iter, &name);
	dbus_message_iter_next(&iter);
	dbus_message_iter_recurse(&iter, &value);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	type = dbus_message_iter_get_arg_type(&value);

	if (g_str_equal(name, "OfflineMode") == TRUE) {
		connman_bool_t offlinemode;

		if (type != DBUS_TYPE_BOOLEAN)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &offlinemode);

		__connman_profile_set_offlinemode(offlinemode);
	} else if (g_str_equal(name, "Country") == TRUE) {
		const char *country;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &country);

		connman_profile_set_country(country);
	} else if (g_str_equal(name, "CheckPortalList") == TRUE) {
		const char *list;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &list);

		__connman_profile_set_checkportal(
		    __connman_service_list_to_mask(list));
		__connman_service_recheck_portal();
	} else if (g_str_equal(name, "PortalURL") == TRUE) {
		const char *url;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &url);

		__connman_profile_set_portal_url(url);
	} else if (g_str_equal(name, "ArpGateway") == TRUE) {
		connman_bool_t arpgateway;

		if (type != DBUS_TYPE_BOOLEAN)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &arpgateway);

		connman_profile_set_arpgateway(arpgateway);
	} else
		return __connman_error_invalid_property(msg);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *get_state(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	const char *str;

	_DBG_MANAGER("conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_PUBLIC) < 0)
		return __connman_error_permission_denied(msg);

	str = online_state();
	return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &str,
						DBUS_TYPE_INVALID);
}

static DBusMessage *create_profile(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	const char *name, *path;
	int err;

	_DBG_MANAGER("conn %p", conn);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name,
							DBUS_TYPE_INVALID);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	err = __connman_profile_create(name, &path);
	if (err < 0)
		return __connman_error_failed(msg, -err);

	return g_dbus_create_reply(msg, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID);
}

static DBusMessage *remove_profile(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	const char *ident;
	int err;

	_DBG_MANAGER("conn %p", conn);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &ident,
							DBUS_TYPE_INVALID);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	err = __connman_profile_remove(ident);
	if (err < 0)
		return __connman_error_failed(msg, -err);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *push_profile(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	const char *ident, *path;
	int err;

	_DBG_MANAGER("conn %p", conn);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &ident,
							DBUS_TYPE_INVALID);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	err = __connman_profile_push(ident, NULL, &path);
	if (err < 0)
		return __connman_error_failed(msg, -err);

	return g_dbus_create_reply(msg, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID);
}

static DBusMessage *__pop_profile(DBusMessage *msg, const char *ident)
{
	int err;

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	err = __connman_profile_pop(ident);
	if (err < 0)
		return __connman_error_failed(msg, -err);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *pop_profile(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	const char *ident;

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &ident,
							DBUS_TYPE_INVALID);

	_DBG_MANAGER("ident %s", ident);

	return __pop_profile(msg, ident);
}

static DBusMessage *pop_any_profile(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	_DBG_MANAGER("");

	return __pop_profile(msg, NULL);
}

static DBusMessage *request_scan(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	enum connman_service_type type;
	const char *str;
	int err;

	_DBG_MANAGER("conn %p", conn);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &str,
							DBUS_TYPE_INVALID);

	if (g_strcmp0(str, "") == 0) {
		/* NB: indicates all wifi and wimax devices */
		type = CONNMAN_SERVICE_TYPE_UNKNOWN;
	} else {
		type = __connman_service_string2type(str);
		if (type == CONNMAN_SERVICE_TYPE_UNKNOWN)
			return __connman_error_invalid_arguments(msg);
	}

	err = __connman_element_request_scan(type);
	if (err < 0) {
		if (err == -EINPROGRESS) {
			connman_error("Invalid return code from scan");
			err = -EINVAL;
		}

		return __connman_error_failed(msg, -err);
	}

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusConnection *connection = NULL;

static enum connman_service_type technology_type;
static connman_bool_t technology_enabled;
static DBusMessage *technology_pending = NULL;
static guint technology_timeout = 0;

static void technology_reply(int error)
{
	_DBG_MANAGER("technology_timeout %d technology_pending %p "
	    "technology_type %d", technology_timeout, technology_pending,
	    technology_type);

	if (technology_timeout > 0) {
		g_source_remove(technology_timeout);
		technology_timeout = 0;
	}

	if (technology_pending != NULL) {
		if (error > 0) {
			DBusMessage *reply;

			reply = __connman_error_failed(technology_pending,
								error);
			if (reply != NULL)
				g_dbus_send_message(connection, reply);
		} else
			g_dbus_send_reply(connection, technology_pending,
							DBUS_TYPE_INVALID);

		dbus_message_unref(technology_pending);
		technology_pending = NULL;
	}

	technology_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
}

static gboolean technology_abort(gpointer user_data)
{
	_DBG_MANAGER("technology_timeout %d technology_pending %p "
	    "technology_type %d", technology_timeout, technology_pending,
	    technology_type);

	technology_timeout = 0;

	technology_reply(ETIMEDOUT);

	return FALSE;
}

static void technology_notify(enum connman_service_type type,
						connman_bool_t enabled)
{
	_DBG_MANAGER("type %d enabled %d technology_timeout %d "
	    "technology_pending %p technology_type %d",
	    type, enabled, technology_timeout, technology_pending,
	    technology_type);

	if (type == technology_type && enabled == technology_enabled)
		technology_reply(0);
}

static struct connman_notifier technology_notifier = {
	.name		= "manager",
	.priority	= CONNMAN_NOTIFIER_PRIORITY_HIGH,
	.service_enabled= technology_notify,
};

static DBusMessage *enable_technology(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	enum connman_service_type type;
	const char *str;
	int err;

	_DBG_MANAGER("conn %p", conn);

	if (technology_pending != NULL) {
		_DBG_MANAGER("pending %p", technology_pending);
		return __connman_error_in_progress(msg);
	}

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &str,
							DBUS_TYPE_INVALID);

	if (g_strcmp0(str, "ethernet") == 0)
		type = CONNMAN_SERVICE_TYPE_ETHERNET;
	else if (g_strcmp0(str, "wifi") == 0)
		type = CONNMAN_SERVICE_TYPE_WIFI;
	else if (g_strcmp0(str, "wimax") == 0)
		type = CONNMAN_SERVICE_TYPE_WIMAX;
	else if (g_strcmp0(str, "bluetooth") == 0)
		type = CONNMAN_SERVICE_TYPE_BLUETOOTH;
	else if (g_strcmp0(str, "cellular") == 0)
		type = CONNMAN_SERVICE_TYPE_CELLULAR;
	else
		return __connman_error_invalid_arguments(msg);

	if (__connman_notifier_is_enabled(type) == TRUE) {
		_DBG_MANAGER("%s already enabled", str);
		return __connman_error_already_enabled(msg);
	}

	technology_type = type;
	technology_enabled = TRUE;
	technology_pending = dbus_message_ref(msg);

	err = __connman_element_enable_technology(type);
	if (err < 0 && err != -EINPROGRESS) {
		technology_reply(-err);
		return NULL;
	}
	/*
	 * The enable work might complete immediately; verify we
	 * actually have a request pending before starting the timer,
	 * otherwise it'll fire and generate a spurious D-Bus reply.
	 */
	if (technology_pending != NULL)
		technology_timeout = g_timeout_add_seconds(15,
		    technology_abort, NULL);

	return NULL;
}

static DBusMessage *disable_technology(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	enum connman_service_type type;
	const char *str;
	int err;

	_DBG_MANAGER("conn %p", conn);

	if (technology_pending != NULL)
		return __connman_error_in_progress(msg);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &str,
							DBUS_TYPE_INVALID);

	type = __connman_service_string2type(str);
	if (type == CONNMAN_SERVICE_TYPE_UNKNOWN)
		return __connman_error_invalid_arguments(msg);

	if (__connman_notifier_is_enabled(type) == FALSE)
		return __connman_error_already_disabled(msg);

	technology_type = type;
	technology_enabled = FALSE;
	technology_pending = dbus_message_ref(msg);

	err = __connman_element_disable_technology(type);
	if (err < 0 && err != -EINPROGRESS) {
		technology_reply(-err);
		return NULL;
	}
	/*
	 * The disable work might complete immediately; verify we
	 * actually have a request pending before starting the timer,
	 * otherwise it'll fire and generate a spurious D-Bus reply.
	 */
	if (technology_pending != NULL)
		technology_timeout = g_timeout_add_seconds(10,
		    technology_abort, NULL);

	return NULL;
}

static DBusMessage *get_vpn_service(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	DBusMessage *reply;
	struct connman_service *service;
	const char *path;
	connman_bool_t created;

	DBG(DBG_MANAGER | DBG_SERVICE, "conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	reply = __connman_provider_create(msg, &service, &created);
	if (reply != NULL)
		return reply;
	if (service == NULL)
		return __connman_error_internal_error(msg);

	reply = __connman_service_set_properties(msg, service);
	if (reply != NULL) {
		if (created == TRUE)
			__connman_service_put(service);
		return reply;
	}

	path = __connman_service_get_path(service);
	return g_dbus_create_reply(msg, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID);
}

static DBusMessage *get_service(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	DBG(DBG_MANAGER | DBG_SERVICE, "conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	return __connman_get_service(msg);
}

static DBusMessage *configure_service(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	DBG(DBG_MANAGER | DBG_SERVICE, "conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	return __connman_configure_service(msg);
}

static DBusMessage *register_agent(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	const char *sender, *path;
	int err;

	_DBG_MANAGER("conn %p", conn);

	sender = dbus_message_get_sender(msg);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID);

	err = __connman_agent_register(sender, path);
	if (err < 0)
		return __connman_error_failed(msg, -err);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *unregister_agent(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	const char *sender, *path;
	int err;

	_DBG_MANAGER("conn %p", conn);

	sender = dbus_message_get_sender(msg);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID);

	err = __connman_agent_unregister(sender, path);
	if (err < 0)
		return __connman_error_failed(msg, -err);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *get_debug_tags(DBusConnection *conn,
                                  DBusMessage *msg, void *data)
{
        DBusMessage *reply;
        char *taglist;
	extern gchar *__connman_debug_get_tags();

	_DBG_MANAGER("conn %p", conn);

        taglist = __connman_debug_get_tags();
        reply = g_dbus_create_reply(msg, DBUS_TYPE_STRING, &taglist,
                                    DBUS_TYPE_INVALID);
        g_free(taglist);
        return reply;
}

static DBusMessage *list_debug_tags(DBusConnection *conn,
				    DBusMessage *msg, void *data)
{
        DBusMessage *reply;
        char *taglist;
	extern gchar *__connman_debug_get_all_tags();

	_DBG_MANAGER("conn %p", conn);

        taglist = __connman_debug_get_all_tags();
        reply = g_dbus_create_reply(msg, DBUS_TYPE_STRING, &taglist,
                                    DBUS_TYPE_INVALID);
        g_free(taglist);
        return reply;
}

static DBusMessage *set_debug_tags(DBusConnection *conn,
                                  DBusMessage *msg, void *data)
{
	char *taglist;

	_DBG_MANAGER("conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &taglist,
                              DBUS_TYPE_INVALID);

	connman_info("Set debug tags \"%s\"", taglist);
	if (!__connman_debug_set_tags(taglist))
		return __connman_error_invalid_debug_tag(msg);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *get_debugmask(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	uint32_t mask = (uint32_t) __connman_debug_getmask();

	_DBG_MANAGER("conn %p", conn);

	return g_dbus_create_reply(msg, DBUS_TYPE_UINT32, &mask,
							DBUS_TYPE_INVALID);
}

static DBusMessage *set_debugmask(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	uint32_t mask;

	_DBG_MANAGER("conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &mask,
							DBUS_TYPE_INVALID);

	connman_info("Set debug mask 0x%x", mask);
	(void) __connman_debug_setmask(mask);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *get_service_order(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	char *str;
	DBusMessage *m;

	_DBG_MANAGER("conn %p", conn);

	str = __connman_service_get_ordering();
	m = g_dbus_create_reply(msg, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID);
	g_free(str);

	return m;
}

static DBusMessage *set_service_order(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	const char *str;

	_DBG_MANAGER("conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &str,
							DBUS_TYPE_INVALID);

	connman_info("Set service order %s", str);
	if (__connman_service_set_ordering(str) < 0)
		return __connman_error_invalid_arguments(msg);

	/* TODO(sleffler) update state (e.g. routing) */

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *recheck_portal(DBusConnection *conn,
				   DBusMessage *msg, void *data)
{
	__connman_service_recheck_portal();

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static GDBusMethodTable manager_methods[] = {
	{ "GetProperties",     "",      "a{sv}", get_properties     },
	{ "SetProperty",       "sv",    "",      set_property       },
	{ "GetState",          "",      "s",     get_state          },
	{ "CreateProfile",     "s",     "o",     create_profile     },
	{ "RemoveProfile",     "s",     "",      remove_profile     },
	{ "PushProfile",       "s",     "o",     push_profile       },
	{ "PopProfile",        "s",     "",      pop_profile        },
	{ "PopAnyProfile",     "",      "",      pop_any_profile    },
	{ "RequestScan",       "s",     "",      request_scan       },
	{ "EnableTechnology",  "s",     "",      enable_technology,
						G_DBUS_METHOD_FLAG_ASYNC },
	{ "DisableTechnology", "s",     "",      disable_technology,
						G_DBUS_METHOD_FLAG_ASYNC },
	{ "GetService",        "a{sv}", "o",     get_service },
	{ "GetWifiService",    "a{sv}", "o",     get_service },
	{ "ConfigureWifiService", "a{sv}","",    configure_service },
	{ "ConfigureService",  "a{sv}", "o",     configure_service },
	{ "GetVPNService",     "a{sv}", "o",     get_vpn_service },
	{ "RegisterAgent",     "o",     "",      register_agent     },
	{ "UnregisterAgent",   "o",     "",      unregister_agent   },
	{ "GetDebugTags",      "",      "s",     get_debug_tags     },
	{ "SetDebugTags",      "s",     "",      set_debug_tags     },
	{ "ListDebugTags",     "",      "s",     list_debug_tags    },
	{ "GetDebugMask",      "",      "u",     get_debugmask      },
	{ "SetDebugMask",      "u",     "",      set_debugmask      },
	{ "GetServiceOrder",   "",      "s",     get_service_order  },
	{ "SetServiceOrder",   "s",     "",      set_service_order  },
	{ "RecheckPortal",     "",      "",      recheck_portal     },
	{ },
};

static GDBusSignalTable manager_signals[] = {
	{ "PropertyChanged", "sv" },
	{ "StateChanged",    "s"  },
	{ },
};

static DBusMessage *nm_sleep(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	DBusMessage *reply;

	_DBG_MANAGER("conn %p", conn);

	reply = dbus_message_new_method_return(msg);
	if (reply == NULL)
		return NULL;

	dbus_message_append_args(reply, DBUS_TYPE_INVALID);

	return reply;
}

static DBusMessage *nm_wake(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	DBusMessage *reply;

	_DBG_MANAGER("conn %p", conn);

	reply = dbus_message_new_method_return(msg);
	if (reply == NULL)
		return NULL;

	dbus_message_append_args(reply, DBUS_TYPE_INVALID);

	return reply;
}

enum {
	NM_STATE_UNKNOWN = 0,
	NM_STATE_ASLEEP,
	NM_STATE_CONNECTING,
	NM_STATE_CONNECTED,
	NM_STATE_DISCONNECTED
};

static DBusMessage *nm_state(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	DBusMessage *reply;
	dbus_uint32_t state;

	_DBG_MANAGER("conn %p", conn);

	reply = dbus_message_new_method_return(msg);
	if (reply == NULL)
		return NULL;

	if (__connman_connection_is_online() == TRUE)
		state = NM_STATE_CONNECTED;
	else
		state = NM_STATE_DISCONNECTED;

	dbus_message_append_args(reply, DBUS_TYPE_UINT32, &state,
							DBUS_TYPE_INVALID);

	return reply;
}

static GDBusMethodTable nm_methods[] = {
	{ "sleep", "",  "",   nm_sleep        },
	{ "wake",  "",  "",   nm_wake         },
	{ "state", "",  "u",  nm_state        },
	{ },
};

static gboolean nm_compat = FALSE;

static const char *powerman_rule = "type=signal,interface=" POWERMAN_INTF;

static DBusHandlerResult powerman_filter(DBusConnection *conn,
					 DBusMessage *msg, void *data)
{
	const char *member, *state;
	DBusError error;

	if (dbus_message_has_interface(msg, POWERMAN_INTF) == FALSE)
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

	member = dbus_message_get_member(msg);
	if (member == NULL)
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

	if (g_str_equal(member, "PowerStateChanged") == TRUE) {
		dbus_error_init(&error);

		if (dbus_message_get_args(msg, &error, DBUS_TYPE_STRING, &state,
					  DBUS_TYPE_INVALID) == FALSE)
			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;

		_DBG_MANAGER("system state %s", state);
		if (g_str_equal(state, "on") == TRUE) {
			/* We've entered the "on" state from some other state */
			__connman_notifier_system_resume();
		} else if (g_str_equal(state, "standby") == TRUE ||
			   g_str_equal(state, "mem") == TRUE ||
			   g_str_equal(state, "disk") == TRUE) {
			/* We're going into a suspend state */
			__connman_notifier_system_suspend();
		}
	} else if (g_str_equal(member, "SuspendDelay") == TRUE) {
		/* We will be going into a suspend state soon */
		_DBG_MANAGER("system power preparing to suspend");
		__connman_notifier_system_suspend();
	} else if (g_str_equal(member, "StartingShutdown") == TRUE) {
		__connman_notifier_system_shutdown();
	}

	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

int __connman_manager_init(gboolean compat)
{
	_DBG_MANAGER("");

	connection = connman_dbus_get_connection();
	if (connection == NULL)
		return -1;

	if (connman_notifier_register(&technology_notifier) < 0)
		connman_error("Failed to register technology notifier");

	g_dbus_register_interface(connection, CONNMAN_MANAGER_PATH,
					CONNMAN_MANAGER_INTERFACE,
					manager_methods,
					manager_signals, NULL, NULL, NULL);

	if (compat == TRUE) {
		g_dbus_register_interface(connection, NM_PATH, NM_INTERFACE,
					nm_methods, NULL, NULL, NULL, NULL);

		nm_compat = TRUE;
	}

	if (dbus_connection_add_filter(connection,
				       powerman_filter, NULL, NULL) == FALSE) {
		dbus_connection_unref(connection);
		connection = NULL;
		return -1;
	}

	dbus_bus_add_match(connection, powerman_rule, NULL);
	dbus_connection_flush(connection);

	return 0;
}

void __connman_manager_cleanup(void)
{
	_DBG_MANAGER("");

	connman_notifier_unregister(&technology_notifier);

	if (connection == NULL)
		return;

	if (nm_compat == TRUE) {
		g_dbus_unregister_interface(connection, NM_PATH, NM_INTERFACE);
	}

	g_dbus_unregister_interface(connection, CONNMAN_MANAGER_PATH,
						CONNMAN_MANAGER_INTERFACE);

	dbus_bus_remove_match(connection, powerman_rule, NULL);
	dbus_connection_flush(connection);
	dbus_connection_remove_filter(connection, powerman_filter, NULL);

	dbus_connection_unref(connection);
}
